package br.com.iswe.gwt.crud.server.controller;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import br.com.iswe.core.dao.QueryDetails;
import br.com.iswe.core.entidades.gwt.BeanMap;
import br.com.iswe.core.entidades.gwt.RemoteEntityObject;
import br.com.iswe.core.entidades.util.EntidadesUtil;
import br.com.iswe.core.entidades.util.PropertyAssemblerInfo;
import br.com.iswe.core.exp.ISWEBusinessException;
import br.com.iswe.core.facade.FacadeContract;
import br.com.iswe.core.web.util.Converter;
import br.com.iswe.gwt.ui.client.ListBoxInfo;
import br.com.iswe.gwt.ui.client.QueryInfo;
import br.com.iswe.gwt.ui.client.RequestInfo;

public abstract class GwtDomainControllerAbstract<ENTITY extends RemoteEntityObject, FACADE extends FacadeContract<ENTITY>> extends GwtControllerAbstract<ENTITY> implements GwtListBoxController, GwtDomainController<ENTITY> {
	private static final String PROPERTY = "property";
	
	
	protected FACADE facade = null;
	
	public abstract Class<? extends ENTITY> getBeanClass();
	
	public Boolean delete(ENTITY entity){
		//Consulta o bean, para mandar a instancia do mecanismo de persistencia.
		entity = facade.getObject(entity);
		facade.remove(entity);
		return true;
	}

	protected ENTITY doSave(ENTITY bean) {
		
		facade.save(bean);
		//apos salvar assegura q sempre vai realizar o flush
		//para q antes de ser montado os maps para retornar para o cliente
		//as propriedades do tipo version estejam com o valores corretos..
		facade.flush();
		
		return bean;
	}
	
	public ENTITY mapToBean(BeanMap beanMap) {
		return mapToBean(beanMap, getBeanClass());
	}

	/**
	 * Metodo que apartir de uma lista de beans transforma numa lista de Map's
	 * contendo apenas as propriedades especificadas.
	 * @param retorno
	 * @param propriedades
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<BeanMap> getBeanMapList(List dados, PropertyAssemblerInfo propertyAssemblerInfo) {
		List<BeanMap> retorno = new ArrayList<BeanMap>();
		Locale locale = getLocale();
		for (Object bean : dados) {
			BeanMap hashBean = getConverterHandler().getAsMap(bean, locale, propertyAssemblerInfo);
			retorno.add(hashBean);
		}
		return retorno;
	}
	
	/**
	 * Efetua uma consulta para um(1) listBox.
	 * @param listBoxInfo
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public ListBoxInfo listBoxQuery(ListBoxInfo listBoxInfo) {
		PropertyAssemblerInfo propertyAssemblerInfo = PropertyAssemblerInfo.getInstance(getBeanClass(), 
				listBoxInfo.getProperties());
		//Consulta a lista de beans
		BeanMap filtro = listBoxInfo.getFiltro();
		List dados = null;
		QueryDetails queryDetails = new QueryDetails();
		queryDetails.getOrderList().addAll(listBoxInfo.getOrderList());
		if(filtro != null){
			ENTITY bean = mapToBean(filtro, getBeanClass());
			dados = getFacade().getObjectsAssembled(bean, propertyAssemblerInfo, queryDetails);
		}
		else{
			dados = getFacade().getObjectsAssembled(null, propertyAssemblerInfo, queryDetails);
		}
		if(dados.size() > 0){
			listBoxInfo.setItens(getBeanMapList(dados, propertyAssemblerInfo));
		}
		//limpa as propriedades q nao sao necessarias devolver para o client
		listBoxInfo.setFiltro(null);
		listBoxInfo.setControllerName(null);
		return listBoxInfo;
	}

	public List<ENTITY> getAllObjects(QueryInfo queryInfo) {
		//Transforma uma lista de beans em uma lista de HashMap
		PropertyAssemblerInfo propertyAssemblerInfo = PropertyAssemblerInfo.getInstance(getBeanClass(), queryInfo.getListaPropriedades());
		QueryDetails queryDetails = null;
		if( queryInfo.isPagingSetuped() ){
			//retorna a lista de objetos da fachada, com paginacao
			queryDetails = new QueryDetails();
			
			queryDetails.setNumeroPagina(queryInfo.getNumeroPagina());
			queryDetails.setTamanhoPagina(queryInfo.getTamanhoPagina());
			
			queryDetails.getOrderList().addAll(queryInfo.getOrderList());
		}
		else if( queryInfo.getOrderList().size() > 0 ){
			//retorna a lista de objetos da fachada, com paginacao
			queryDetails = new QueryDetails();
			queryDetails.getOrderList().addAll(queryInfo.getOrderList());			
		}
		
		 List<ENTITY> list = facade.getObjectsAssembled(propertyAssemblerInfo, queryDetails);
		
		return list;
	}
	
	public List<ENTITY> getObjects(ENTITY entity, QueryInfo queryInfo) {
		//Transforma uma lista de beans em uma lista de HashMap
		PropertyAssemblerInfo propertyAssemblerInfo = PropertyAssemblerInfo.getInstance(getBeanClass(), queryInfo.getListaPropriedades());
		QueryDetails queryDetails = null;
		if( queryInfo.isPagingSetuped() ){
			//retorna a lista de objetos da fachada, com paginacao
			queryDetails = new QueryDetails();
			
			queryDetails.setNumeroPagina(queryInfo.getNumeroPagina());
			queryDetails.setTamanhoPagina(queryInfo.getTamanhoPagina());
			
			queryDetails.getOrderList().addAll(queryInfo.getOrderList());
		}
		else if( queryInfo.getOrderList().size() > 0 ){
			//retorna a lista de objetos da fachada, com paginacao
			queryDetails = new QueryDetails();
			queryDetails.getOrderList().addAll(queryInfo.getOrderList());			
		}
		
		 List<ENTITY> list = facade.getObjectsAssembled(entity, propertyAssemblerInfo, queryDetails);
		
		return list;
	}
	
	public ENTITY detail(ENTITY entity, QueryInfo queryInfo) {
		PropertyAssemblerInfo propertyAssemblerInfo = PropertyAssemblerInfo.getInstance(getBeanClass(), queryInfo.getListaPropriedades());
		ENTITY result = facade.getObjectAssembled(entity, propertyAssemblerInfo);
		return result;
	}
	
	public ENTITY save(ENTITY entity, RequestInfo requestInfo) {
		//save the bean
		ENTITY savedBean = doSave(entity);
		
		//return to the screen
		return posSalvar(savedBean, requestInfo);
	}
	
	protected ENTITY posSalvar(ENTITY entity, RequestInfo requestInfo) {
		String [] propriedades = null;
		if(requestInfo.getListaPropriedades() != null){
			//Obtem as propriedades a serem retornadas toda vez q salva um registro.
			int tamanho = requestInfo.getListaPropriedades().length;
			propriedades = new String[tamanho + 1];
			System.arraycopy(requestInfo.getListaPropriedades(), 0, propriedades, 0, tamanho);
			propriedades[tamanho] = EntidadesUtil.getPropertyId(entity.getClass()).getName();
		}
		else{
			propriedades = new String[]{ EntidadesUtil.getPropertyId(entity.getClass()).getName() };
		}
		
		PropertyAssemblerInfo propertyAssemblerInfo = PropertyAssemblerInfo.getInstance(entity.getClass(), propriedades);
		
		//assembly the bean before returing to the client layer
		ENTITY result = EntidadesUtil.assemblyBean(entity, propertyAssemblerInfo);
		
		return result;
	}
	
	@SuppressWarnings("unchecked")
	protected ENTITY setupManyToMany(BeanMap mapBean, RequestInfo requestInfo, boolean remover, boolean beanMap) {
		//Verifica os tipos dentro do map de dados
		checkTypeMap(mapBean);
		
		//Carrega o bean com a lista que deve ser associada.
		PropertyDescriptor propertyId = EntidadesUtil.getPropertyId(getBeanClass());
		String valorIdString = mapBean.getString(propertyId.getName());
		//Obtem o valor da chave.
		Object valorId = getConverterHandler().getAsObject(valorIdString, getLocale(), propertyId);
		
		//OBtem a fachada do objeto
		FACADE facade = getFacade();
		//obtem o bean do banco
		ENTITY bean = facade.getObject((Serializable)valorId);
		//Obtem o valor da lista e remove os itens
		String propriedadeLista = (String)mapBean.get(PROPERTY);
		PropertyDescriptor propertyLista = EntidadesUtil.getPropertyDescriptor(getBeanClass(), propriedadeLista);
		List lista = (List)EntidadesUtil.getPropertyValue(bean, propriedadeLista);
		List<BeanMap> listaItens = null;
		
		//Obtem a classe do item da associacao Many to Many
		Class classItem = EntidadesUtil.getManyToManyTargetClass(propertyLista);
		//Propriedades que seram retornadas na lista do bean
		PropertyAssemblerInfo beanPropertiesToReturn = PropertyAssemblerInfo.getInstance(getBeanClass(), requestInfo.getListaPropriedades());
		beanPropertiesToReturn.parsePropriedades(propriedadeLista);//make sure the list property is in the beanPropertiesToReturn

		//caso seja para remover e a lista seja nula retorna
		if(remover && (lista == null || lista.size() == 0)){
			if(beanMap){
				return retulsAsBeanMap(bean, beanPropertiesToReturn, propriedadeLista);
			}
			else{
				return retulsAsEntity(bean, beanPropertiesToReturn, propriedadeLista);
			}
		}
		
		//Obtem o converter responsavel pela classe.
		Converter converter = getServiceLocator().getConverter(classItem);
		//Obtem PropertyDescriptor para a chave da entidade.
		PropertyDescriptor propertyIdItem = EntidadesUtil.getPropertyId(classItem);
		
		//store the remove itens
		List<Object> removedItens = new ArrayList<Object>();
		
		//varre os itens que devem ser removidos/adicionados da lista do bean
		listaItens = (List<BeanMap>)mapBean.get(propriedadeLista);
		//caso a lista esteja nula cria uma nova
		if(lista == null){
			lista = new ArrayList(listaItens.size());
			bean.setPropertyValue(propriedadeLista, lista);
		}
		for(RemoteEntityObject entity : listaItens){
			
			Object idItem = entity.getPropertyValue(propertyIdItem.getName());
			if(idItem instanceof String){
				String idItemString = (String)idItem;
				idItem = getConverterHandler().getAsObject(idItemString, getLocale(), propertyIdItem);
			}
			
			Object item = converter.getAsObject(idItem.toString());
			//sempre remove da lista original, mesmo que seja para adicionar, para evitar itens duplicados.
			lista.remove(item);
			if( ! remover){
				//caso nao seja apenas para remover , adiciona novamente a lista
				lista.add(item);
			}
			else{
				removedItens.add(item);
			}
		}

		//Extension point
		saveManyToManyModifiedList(bean, propertyLista, lista, removedItens);

		//realiza o flush antes de gerar o retorno
		facade.flush();
		
		facade.evict(bean);
		
		bean = facade.getObject((Serializable)valorId);
		
		if(beanMap){
			return retulsAsBeanMap(bean, beanPropertiesToReturn, propriedadeLista);
		}
		else{
			return retulsAsEntity(bean, beanPropertiesToReturn, propriedadeLista);
		}
	}

	@SuppressWarnings("unchecked")
	protected ENTITY retulsAsBeanMap(ENTITY bean, PropertyAssemblerInfo beanPropertiesToReturn, String propriedadeLista) {
		//Obtem o valor da lista e remove os itens
		List lista = (List)EntidadesUtil.getPropertyValue(bean, propriedadeLista);
		
		PropertyAssemblerInfo listPropertiesToReturn = null;
		List<PropertyAssemblerInfo> complexProperties = beanPropertiesToReturn.getComplexProperties();
		for(PropertyAssemblerInfo assemblerInfo : complexProperties){
			if( propriedadeLista.equals( assemblerInfo.getPropertyDescriptor().getName() ) ){
				listPropertiesToReturn = assemblerInfo;
				break;
			}
		}
		if(listPropertiesToReturn == null){
			throw new ISWEBusinessException("br.com.iswe.gwt.crud.server.GwtBeanMapController.afterSetupManyToMany.list.properties.not.found");
		}
		
		List<BeanMap> listaItens = getBeanMapList(lista, listPropertiesToReturn);
		
		//monta o bean de retorno
		
		//limpa as propriedades complexas do beanProperties
		beanPropertiesToReturn.clearComplexProperties();
		
		BeanMap mapBean = getConverterHandler().getAsMap(bean, getLocale(), beanPropertiesToReturn);
		
		//seta a lista de retorno
		mapBean.put(propriedadeLista, listaItens);

		return (ENTITY)mapBean;
	}
	
	protected ENTITY retulsAsEntity(ENTITY bean, PropertyAssemblerInfo beanPropertiesToReturn, String propriedadeLista) {
		ENTITY retorno = EntidadesUtil.assemblyBean(bean, beanPropertiesToReturn);
		return retorno;
	}
	
	public ENTITY removerManyToManyAsEntity(BeanMap mapBean, RequestInfo requestInfo) {
		return setupManyToMany(mapBean, requestInfo, true, false);
	}
	
	public ENTITY associarManyToManyAsEntity(BeanMap mapBean, RequestInfo requestInfo) {
		return setupManyToMany(mapBean, requestInfo, false, false);
	}

	/**
	 * Save the master object with the modified list
	 * @param bean
	 * @param modifiedListProperty 
	 * @param newLIst
	 * @param removedItensList
	 */
	protected void saveManyToManyModifiedList(ENTITY bean, PropertyDescriptor modifiedListProperty, List<Object> newList, List<Object> removedItensList) {
		facade.save(bean);
	}

	public FACADE getFacade() {
		return facade;
	}

	public void setFacade(FACADE facade) {
		this.facade = facade;
	}
}
